﻿using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Pasta.Medio
{
	public delegate void DriveDetectorEventHandler(Object sender, DriveDetectorEventArgs e);

	/// <summary>
	/// Hidden form to receive Windows messages about flash drives
	/// </summary>
	internal class DetectorForm : Form
	{
		private DriveDetector _detector = null;

		/// <summary>
		/// Set up the hidden form. 
		/// </summary>
		/// <param name="detector">DriveDetector object to receive notification about flash drives</param>
		public DetectorForm(DriveDetector detector)
		{
			_detector = detector;
			MinimizeBox = false;
			MaximizeBox = false;
			ShowInTaskbar = false;
			ShowIcon = false;
			FormBorderStyle = FormBorderStyle.None;
			Load += LoadForm;
			Activated += FormActivated;
		}

		private void LoadForm(object sender, EventArgs e)
		{
			InitializeComponent();
			Size = new System.Drawing.Size(5, 5);
		}

		private void FormActivated(object sender, EventArgs e)
		{
			Visible = false;
		}

		/// <summary>
		/// Call the DriveDetector from to pick up the messages about flash drives
		/// </summary>
		protected override void WndProc(ref Message m)
		{
			base.WndProc(ref m);

			if (_detector != null)
			{
				_detector.WndProc(ref m);
			}
		}

		private void InitializeComponent()
		{
			this.SuspendLayout();
			// 
			// DetectorForm
			// 
			this.ClientSize = new System.Drawing.Size(1, 1);
			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
			this.Name = "DetectorForm";
			this.ResumeLayout(false);
		}
	}


	/// <summary>
	/// Event arguments for flash drive events
	/// </summary>
	public class DriveDetectorEventArgs : EventArgs
	{
		public DriveDetectorEventArgs(string drive = "")
		{
			Drive = drive;
		}

		/// <summary>
		/// Drive letter for the device which caused this event 
		/// </summary>
		public string Drive;
	}


	/// <summary>
	/// Detects insertion or removal of removable drives.
	/// Create an instance of this class in your project and add handlers for the
	/// DriveInserted and DriveRemoved events.
	/// </summary>
	class DriveDetector : IDisposable
	{
		/// <summary>
		/// Events signalized to the client app. Add your handlers.
		/// </summary>
		public event DriveDetectorEventHandler DriveInserted;
		public event DriveDetectorEventHandler DriveRemoved;

		/// <summary>
		/// Create a hidden form for processing Windows messages about USB drives
		/// </summary>
		public DriveDetector()
		{
			DetectorForm form = new DetectorForm(this);
			form.Show();
		}

		/// <summary>
		/// Called by GC
		/// </summary>
		public void Dispose()
		{
		}

		/// <summary>
		/// Message handler called from client form.
		/// </summary>
		/// <param name="m"></param>
		public void WndProc(ref Message m)
		{
			int devType;
			char c;

			if (m.Msg == wmDeviceChange)
			{
				switch (m.WParam.ToInt32())
				{
					// new device inserted
					case wmDriveInserted:
						devType = Marshal.ReadInt32(m.LParam, 4);
						if (devType == wmDriveIsVolume && DriveInserted != null)
						{
							BroadcastVolume vol = (BroadcastVolume)Marshal.PtrToStructure(m.LParam, typeof(BroadcastVolume));
							c = DriveMaskToLetter(vol.dbcv_unitmask);
							DriveDetectorEventArgs e = new DriveDetectorEventArgs(c + ":\\");
							DriveInserted(this, e);
						}
						break;

					// Device has been removed
					case wmDriveRemoved:
						devType = Marshal.ReadInt32(m.LParam, 4);
						if (devType == wmDriveIsVolume && DriveRemoved != null)
						{
							BroadcastVolume vol = (BroadcastVolume)Marshal.PtrToStructure(m.LParam, typeof(BroadcastVolume));
							c = DriveMaskToLetter(vol.dbcv_unitmask);
							DriveDetectorEventArgs e = new DriveDetectorEventArgs(c + ":\\");
							DriveRemoved(this, e);
						}
						break;
				}
			}
		}

		/// <summary>
		/// Gets drive letter from a bit mask where bit 0 = A, bit 1 = B etc.
		/// Use the last bit set.
		/// </summary>
		/// <param name="mask"></param>
		/// <returns></returns>
		private static char DriveMaskToLetter(int mask)
		{
			string drives = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
			int cnt = 0;
			int pom = mask / 2;
			while (pom != 0)
			{
				pom = pom / 2;
				cnt++;
			}
			return (cnt < drives.Length) ? drives[cnt] : '?';
		}

		private const int wmDeviceChange = 0x0219;
		private const int wmDriveInserted = 0x8000;
		private const int wmDriveRemoved = 0x8004;
		private const int wmDriveIsVolume = 0x00002; // drive type is logical volume

		[StructLayout(LayoutKind.Sequential)]
		public struct BroadcastVolume
		{
			public int dbcv_size;
			public int dbcv_devicetype;
			public int dbcv_reserved;
			public int dbcv_unitmask;
		}
	}
}
